<#if packagePath != ''>package ${packagePath}</#if>;

import <#if packagePath != ''>${packagePath}.</#if>annotation.BeanMapping;
import <#if packagePath != ''>${packagePath}.</#if>convert.Convert;
import <#if packagePath != ''>${packagePath}.</#if>convert.ConvertException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author cxx
 **/
public class BeanMappingUtil {

    public static <S,T> List<T> toBeanMappingList(List<S> sources, Class<T> targetClass){

        if(sources == null || sources.size () == 0){
            return Collections.EMPTY_LIST;
        }

        return sources.stream ().map (source -> (T)toBeanMapping (source, targetClass, true)).collect (Collectors.toList ());

    }

    public static <S,T> T toBeanMapping(S source,T target){
        return toBeanMapping(source,target,true);
    }

    public static <S,T> T toBeanMapping(S source,Class<T> targetCls){

        return toBeanMapping(source,targetCls,true);
    }

    private static <S,T> T toBeanMapping(S source,Object target,boolean isMethodPriority){
        try {
            T instance = null;
            if(target instanceof Class){
                Class targetCls = (Class) target;
                Constructor<T> constructor = targetCls.getConstructor ();
                instance = constructor.newInstance ();
            }else{
                instance = (T) target;
            }
            Field[] fields = instance.getClass ().getDeclaredFields ();
            if (fields.length == 0){
                return instance;
            }
            for (Field field : fields) {
                field.setAccessible (true);
                BeanMapping[] beanMappings = field.getAnnotationsByType (BeanMapping.class);
                if (beanMappings.length == 0) {
                    if(isMethodPriority){
                        defHandleFieldValueByPriorityMethod (source, instance, field);
                    }
                }else{
                    Map<Class,BeanMapping> resembleMap = new HashMap<> (1);
                    for (BeanMapping beanMapping : beanMappings) {
                        for (Class cls : beanMapping.resembles ()) {
                            if(cls == Object.class){
                                resembleMap.put (Object.class,beanMapping);
                            }else{
                                BeanMapping mapping = resembleMap.get (cls);
                                if(mapping != null){
                                    String filedName = mapping.value ();
                                    if(!"".equals (filedName) && filedName.equals (beanMapping.value ())){
                                        throw new NoSuchFieldError ("exist multiple field name");
                                    }
                                }
                                resembleMap.put (cls,beanMapping);
                            }
                        }
                    }
                    BeanMapping beanMapping = resembleMap.get (source.getClass ());
                    if(beanMapping == null){
                        beanMapping = resembleMap.get (Object.class);
                    }
                    Class obj = null;
                    if(beanMapping == null){
                        obj = Object.class;
                    }else{
                        if (beanMapping.ignore ()) {
                            continue;
                        }
                        obj = beanMapping.resembles ()[0];
                    }
                    if(obj == Object.class){
                        if(isMethodPriority){
                            defHandleFieldValueByPriorityMethod (source, instance, field);
                        }
                    }else{
                        if(isMethodPriority){
                            if (obj.isInstance (source)) {
                                handlerFieldValueByPriorityMethod(source,instance,field,resembleMap.get (obj));
                            }
                        }
                    }
                }
            }
            return instance;
        } catch (Exception e) {

        }
        return null;
    }

    private static <S, T> void handlerFieldValueByPriorityMethod(S source, T instance, Field field, BeanMapping beanMapping) {
        String fname = "".equals (beanMapping.value ())?field.getName ():beanMapping.value ();

        Method method = null;
        Object fv = null;
        try {
            method = source.getClass ().getMethod ("get" + initialsToUpperCase (fname));
            fv = method.invoke (source);
            if (method.getReturnType () != field.getType ()) {
                fv = wapper(fv,beanMapping.wrapperConvert ());
            }
        }catch (ConvertException ce){
            throw ce;
        }catch (Exception e) {
            //ignore
        }
        if(fv != null){
            Method setMethod = null;
            try {
                setMethod = instance.getClass ().getMethod ("set" + initialsToUpperCase (field.getName ()),field.getType ());
                setMethod.invoke (instance,fv);
                return;
            } catch (Exception e) {
                //ignore
            }
            if(setMethod == null){
                try {
                    field.set (instance,fv);
                } catch (Exception e) {
                    //ignore
                }
            }
        }
    }

    private static Object wapper(Object fv, Class<? extends Convert> convert) {

        if(convert.isInterface () || Modifier.isAbstract (convert.getModifiers ())){
            return fv;
        }
        try {
            Convert instance = convert.newInstance ();
            fv = instance.convert (fv);
        }catch (ConvertException ce){
            throw ce;
        }catch (Exception e) {
            //ignore
        }
        return fv;
    }

    private static <S, T> void defHandleFieldValueByPriorityMethod(S source,T instance, Field field) {
        Method method = null;
        Object fv = null;
        try {
            method = source.getClass ().getMethod ("get" + initialsToUpperCase (field.getName ()));
            if (method.getReturnType () != field.getType ()) { //字段类型不同不做处理
                method = null;
            }
            fv = method.invoke (source);
        } catch (Exception e) {
            //ignore
        }
        if(method == null){
            Field sf = null;
            try {
                sf = source.getClass ().getField (field.getName ());
            } catch (NoSuchFieldException e) {
                //ignore
                return;
            }
            if (sf.getType () != field.getType ()) { //字段类型不同不做处理
                return;
            }
            try {
                fv = sf.get (source);
            } catch (Exception e) {
                //ignore
                return;
            }

        }
        if(fv != null){
            Method setMethod = null;
            try {
                setMethod = instance.getClass ().getMethod ("set" + initialsToUpperCase (field.getName ()),field.getType ());
                setMethod.invoke (instance,fv);
            } catch (Exception e) {
                //ignore
            }
            if(setMethod == null){
                try {
                    field.set (instance,fv);
                } catch (Exception e) {
                    //ignore
                }
            }
        }
    }

    private static String initialsToUpperCase(String letter){
        if (letter == null || letter.isEmpty ()) {
            return letter;
        }
        char[] chars = letter.toCharArray ();
        char c = letter.charAt (0);
        if(c >= 97 && c <= 122){
            char C = (char)(c - 32);
            chars[0] = C;
            return  new String (chars);
        }
        return letter;
    }

}
